"""
比赛管理模块
"""
import json
import time
from collections import OrderedDict, defaultdict
from datetime import datetime
from typing import List

from flask import current_app, request
from flask_restx import abort, reqparse
from sqlalchemy import func

from app.api.v1.participant.base_view import ParticipantBase
from app.common.range_manager import RangeManager
from app.models import ctfModel
from app.models.ctfModel import (
    CtfRedBlue,
    CtfRedBlueFlag,
    CtfRedBlueGroup,
    CtfRedBlueManualAudit,
    CtfRedBlueScore,
    GroupType,
    LinkCtfRedBlueScoreInfo, CtfNotice,
)
from app.models.rangeModel import RangeLinkModule, RangeVm
from app.models.userModel import User
from tools import db
from utils.utils import base_detail, init_opserver_conn


class MyMath(ParticipantBase):

    # 获取用户信息
    def getMyCtfInfo(self, id=None):
        ctfInfo = []
        if id:
            ctfInfo = ctfModel.CtfLinkUser.query.filter_by(
                ctf_id=id, user_id=self.user.id
            ).first()
        return ctfInfo

    # 查看是否已经答题
    def getMyCtfMathFlag(self, ctf_id, ctf_link_question_id):
        # 判断传入参数是否正确
        ctfInfo = self.getMyCtfInfo(id=ctf_id)

        if not ctfInfo.id:
            abort(400, message="参数错误")

        linkInfo = ctfModel.CtfLinkQuestion.query.filter_by(
            ctf_id=ctf_id, id=ctf_link_question_id
        ).first()

        if not linkInfo:
            abort(400, message="无关联信息")

        info = []
        info = ctfModel.CtfUserFlag.query.filter_by(
            ctf_id=ctf_id,
            ctf_link_question_id=ctf_link_question_id,
            user_id=self.user.id,
        ).first()
        return info, linkInfo

    # 根据关卡查询题目
    def getMyCtfMathBylevelNumber(self, ctf_id, level_number):
        # 判断传入参数是否正确
        ctfInfo = self.getMyCtfInfo(id=ctf_id)
        if not ctfInfo.id:
            abort(400, message="参数错误")

        if ctfInfo.ctfInfo.status != 1:
            abort(400, message="比赛未开启或比赛已过期，不能进行答题")

        level_question = ctfModel.CtfLinkQuestion.query.filter_by(
            ctf_id=ctf_id, level_number=level_number
        ).all()

        return level_question

    # 根据ctf 统计关卡得分
    def getMyCtfMathlevelNumberSore(self, ctf_id):
        # score = ctfModel.CtfUserFlag.query.filter_by(ctf_id=ctf_id,user_id=self.user.id)
        # .group_by(ctfModel.CtfUserFlag.level_number).all()
        numberScore = db.session.execute(
            "select sum(score) as numberScore,level_number from api_ctf_user_flag where user_id="
            + str(self.user.id)
            + " and ctf_id="
            + str(ctf_id)
            + " group by level_number"
        )
        item = {}

        if numberScore:
            for i in numberScore:
                item[i[1]] = int(i[0])

        return item

    @staticmethod
    def check_ctf_red_blue_exists(ctf_red_blue_id) -> CtfRedBlue:
        """ """
        if not ctf_red_blue_id:
            abort(400, message="比赛ID不存在！")
        ctf_red_blue = CtfRedBlue.query.filter(CtfRedBlue.id == ctf_red_blue_id)
        if ctf_red_blue.count() != 1:
            abort(400, message="当前比赛不存在！")
        return ctf_red_blue.first()

    @staticmethod
    def check_ctf_red_blue_is_me(user_id, ctf_id) -> CtfRedBlueGroup:
        group = CtfRedBlueGroup.query.filter(
            CtfRedBlueGroup.relate_user.any(User.id == user_id),
            CtfRedBlueGroup.ctf_red_blue_id == ctf_id,
        )
        if group.count() != 1:
            abort(400, message="该比赛不是你参与的比赛")
        return group.first()

    @staticmethod
    def group_score(group: CtfRedBlueGroup, initial_score=0):
        """计算团队总成绩"""
        score_info: List[LinkCtfRedBlueScoreInfo] = group.link_score
        score_record = [item.score_info for item in score_info]

        score_result = defaultdict(list)
        for item in score_record:
            if item:
                score_result[item.change_type].append(item.score)

        plus_score = sum(score_result[CtfRedBlueScore.ChangeTypeEnum.plus.name] or [0])
        less_score = sum(score_result[CtfRedBlueScore.ChangeTypeEnum.less.name] or [0])

        return initial_score + plus_score - less_score

    @staticmethod
    def ctf_red_blue_status_map(ctf_red_blue: CtfRedBlue, msg=""):
        """比赛状态对应检查映射"""
        status_map = {
            CtfRedBlue.StatusChoices.wait_start.value: f"比赛未开始 {msg}",
            CtfRedBlue.StatusChoices.in_progress.value: "比赛进行中",
            CtfRedBlue.StatusChoices.closed.value: f'比赛已结束，{"获胜方：" + ctf_red_blue.king if ctf_red_blue.king else ""} {msg}',  # noqa
        }
        return status_map.get(ctf_red_blue.status, "未知")


class MyCTFmathList(MyMath):
    def get(self):
        params = request.args
        page = params.get("page", 1)
        keyword = params.get("keyword")
        status = 0
        status = params.get("status")
        page_size = params.get("pageSize", 10)
        try:
            page = int(page)
            page_size = int(page_size)
        except:  # noqa
            page = 1
            page_size = 10

        if page_size >= 50:
            abort(400, message="每页数量不能超过50")

        try:
            filters = []
            if keyword:
                # 添加关键词的检索选项
                filters.append(ctfModel.Ctf.name.contains(keyword))

            if status:
                if int(status) not in [0, 1, 2]:
                    abort(400, message="参数错误")
                filters.append(ctfModel.Ctf.status == status)

            paginate = (
                ctfModel.CtfLinkUser.query.join(
                    ctfModel.Ctf, ctfModel.Ctf.id == ctfModel.CtfLinkUser.ctf_id
                )
                .filter(ctfModel.CtfLinkUser.user_id == self.user.id, *filters)
                .order_by(ctfModel.CtfLinkUser.id.desc())
                .paginate(page, page_size, error_out=False)
            )
            info = {}
            info["current_page"] = page
            info["data"] = []
            if len(paginate.items) > 0:
                for content in paginate.items:
                    info["data"].append(
                        {
                            "name": content.ctfInfo.name,
                            "describe": content.ctfInfo.describe,
                            "start_date": content.ctfInfo.start_date,
                            "end_date": content.ctfInfo.end_date,
                            "type": content.ctfInfo.type,
                            "id": content.ctfInfo.id,
                            "range_uuid": content.ctfInfo.range_uuid,
                            "status": content.ctfInfo.status,
                        }
                    )
            info = base_detail(info, paginate)
            return {"code": 200, "data": info}

        except Exception as e:
            current_app.logger.exception(e)
            abort(500, message="参数错误")


class MyCTFmath(MyMath):
    def get(self, ctf_id):
        if not ctf_id:
            abort(400, message="参数错误")

        # 判断是否为我的
        ctfinfo = self.getMyCtfInfo(id=ctf_id)
        if not ctfinfo:
            abort(400, message="ctf信息不存在")

        # 查询当前
        ctfLevel = ctfModel.CtfLevel.query.filter_by(ctf_id=ctf_id).first()
        numberLevel = self.getMyCtfMathlevelNumberSore(ctf_id=ctf_id)

        return {
            "code": 200,
            "data": {
                "name": ctfinfo.ctfInfo.name,
                "numberLevel": numberLevel,
                "ctf_id": ctfinfo.ctfInfo.id,
                "start_date": ctfinfo.ctfInfo.start_date,
                "end_date": ctfinfo.ctfInfo.end_date,
                "level": ctfinfo.ctfInfo.level,
                "levelScore": ctfLevel.level_number_score,
            },
        }


class MyCTFLevel(MyMath):
    def post(self):
        parser = reqparse.RequestParser()
        parser.add_argument("ctf_id", type=int, help="ctf_id有误", required=True)
        parser.add_argument("level_number", type=int, help="关卡", required=True)
        args = parser.parse_args()

        if not all([args["ctf_id"], args["level_number"]]):
            abort(400, message="参数错误")

        level_question = self.getMyCtfMathBylevelNumber(
            ctf_id=args["ctf_id"], level_number=args["level_number"]
        )
        if not level_question:
            abort(400, message="参数错误")

        info = []
        for q in level_question:
            myCtfAnswer, linkQuestion = self.getMyCtfMathFlag(
                ctf_id=args["ctf_id"], ctf_link_question_id=q.id
            )

            # 更新答题内容
            if myCtfAnswer:
                info.append(
                    {
                        "id": q.id,
                        "ctf_link_question_id": q.id,
                        "ctf_id": q.ctf_id,
                        "questionInfo": [_.to_dict() for _ in q.question],
                        "submitQuestion": [
                            {
                                "id": myCtfAnswer.id,
                                "submit_flag": myCtfAnswer.submit_flag,
                                "ctf_id": myCtfAnswer.ctf_id,
                            }
                        ],
                    }
                )
            else:
                info.append(
                    {
                        "id": q.id,
                        "ctf_link_question_id": q.id,
                        "ctf_id": q.ctf_id,
                        "questionInfo": [_.to_dict() for _ in q.question],
                        "submitQuestion": "",
                    }
                )

        return {"code": 200, "data": info}


class MyCTFmathFlag(MyMath):
    # 开始答题
    def post(self):
        parser = reqparse.RequestParser()
        parser.add_argument("ctf_id", type=int, help="ctf_id有误", required=True)
        parser.add_argument(
            "ctf_link_question_id", type=int, help="id有误", required=True
        )
        parser.add_argument("submit_flag", type=str, help="填写flag", required=True)
        args = parser.parse_args()

        if not all([args["ctf_id"], args["ctf_link_question_id"], args["submit_flag"]]):
            abort(400, message="参数错误")

        myCtfAnswer, linkQuestion = self.getMyCtfMathFlag(
            ctf_id=args["ctf_id"], ctf_link_question_id=args["ctf_link_question_id"]
        )
        if myCtfAnswer:
            abort(400, message="参数错误")

        score = linkQuestion.question[0].score  # 分数
        answer = linkQuestion.question[0].answer.strip()  # 答案
        name = linkQuestion.question[0].name  # 赛题名称

        if args["submit_flag"].strip() == answer:
            with db.auto_commit():
                # 开启答题
                obj = ctfModel.CtfUserFlag(
                    ctf_id=args["ctf_id"],
                    ctf_link_question_id=args["ctf_link_question_id"],
                    submit_flag=args["submit_flag"],
                    score=score,
                    level_number=linkQuestion.level_number,
                    user_id=self.user.id,
                )
                ctfModel.db.session.add(obj)

                # 统计总分 及 答题数 反写回ctf用户表
                total = (
                    db.session.query(
                        db.func.count(ctfModel.CtfUserFlag.ctf_link_question_id),
                        db.func.sum(ctfModel.CtfUserFlag.score),
                    )
                    .filter(
                        ctfModel.CtfUserFlag.ctf_id == args["ctf_id"],
                        ctfModel.CtfUserFlag.user_id == self.user.id,
                    )
                    .all()
                )
                ctfModel.CtfLinkUser.query.filter_by(
                    ctf_id=args["ctf_id"], user_id=self.user.id
                ).update({"questions": total[0][0], "totalScore": total[0][1]})
                # 记录学生提交flag记录
                timeArray = time.localtime(time.time())
                checkpoint = time.strftime("%Y-%m-%d %H:%M:%S", timeArray)
                # audit_log = checkpoint + " "*2 + self.user.username + " "*2 + "提交赛题" + ":" + name + " "*2 + \
                #             args["submit_flag"].strip()
                flag_log_obj = ctfModel.CtfUserFlagAuditLog(
                    ctf_id=args["ctf_id"],
                    checkpoint=checkpoint,
                    stu_name=self.user.username,
                    log_str="提交赛题",
                    question_name=name,
                    question_flag=args["submit_flag"].strip(),
                    audit_score=score
                )
                ctfModel.db.session.add(flag_log_obj)
            return {"code": 200, "data": "恭喜flag提交正确"}
        else:
            abort(400, message="提交错误，请继续作答")

        # if myCtfAnswer:
        #    # 更新操作
        #    ctfModel.CtfUserAnswer.query.filter_by(ctf_id=args['ctf_id'],ctf_link_question_id=args['ctf_link_question_id'], user_id=self.user.id).update(  # noqa
        #        {"submit_flag": args['submit_flag']})
        #    ctfModel.db.session.commit()
        #    return


class MyCTFRedBlue(MyMath):
    def get(self, ctf_red_blue_id=None):

        ctf: CtfRedBlue = self.check_ctf_red_blue_exists(ctf_red_blue_id)

        # 判断是否为我的
        self.check_ctf_red_blue_is_me(self.user.id, ctf.id)

        if ctf.status != CtfRedBlue.StatusChoices.in_progress.value:
            abort(400, message=self.ctf_red_blue_status_map(ctf, msg="无法查看比赛信息"))

        if not ctf.range_uuid:
            abort(400, message="该比赛未关联靶场")

        # 获取比赛关联靶场信息
        range_link_module_info = RangeLinkModule.query.filter(
            RangeLinkModule.range_uuid == ctf.range_uuid
        ).first()

        if not range_link_module_info:
            abort(400, message="靶场不存在")

        # 获取入口虚拟机
        entry_range_vm = RangeVm.query.filter(
            RangeVm.is_external == 1,
            RangeVm.range_uuid == range_link_module_info.range_uuid,
        ).first()

        if not entry_range_vm:
            abort(400, message="该靶场未配置入口服务")

        # 获取入口IP
        if (
            ctf.status == ctf.StatusChoices.in_progress.value
            and not entry_range_vm.external_ip
            and entry_range_vm.op_vm_uuid
        ):
            op_conn = init_opserver_conn()

            info = op_conn.compute.get_server(entry_range_vm.op_vm_uuid)
            addresses = list(info.addresses.values())[1][0]["addr"]
            entry_range_vm.external_ip = addresses
            db.session.add(entry_range_vm)
            db.session.commit()

        external_ip = entry_range_vm.external_ip

        return {
            "code": 200,
            "data": external_ip,
            "ctf_info": {
                "id": ctf.id,
                "name": ctf.name,
                "start_date": ctf.start_date,
                "end_date": ctf.end_date,
                "range_uuid": ctf.range_uuid,
                "king": ctf.king,
            },
        }


class MyCTFNotice(MyMath):

    def get(self):
        params = request.args
        notice_id = params.get("id", type=int)
        page = params.get("page", type=int, default=1)
        page_size = params.get("pageSize", type=int, default=10)
        is_read = params.get("is_read", type=int, default=0)
        user_id = self.user.id
        result_data_list = []
        # 学生查看单个公告详情
        if notice_id:
            row = db.session.query(CtfNotice).filter_by(id=notice_id).first()
            if row.notice_status_info:
                no = json.loads(row.notice_status_info)
                if str(user_id) not in  list(no.keys()):
                    return {"code": 200, "message": "没有查看权限！"}
                no['id_{}'.format(user_id)] = 1
                row.notice_status_info = json.dumps(no)
                db.session.commit()
            result_data = {
                "id": row.id,
                "title": row.title,
                "content": row.content,
                "level": row.level,
                "create_time": str(row.create_time)
            }
            return {"code": 200, "data": result_data}

        notice_rows = db.session.query(CtfNotice).order_by(CtfNotice.create_time.desc()).all()
        if is_read ==0:
            for notice in notice_rows:
                if str(user_id) not in list(json.loads(notice.notice_status_info).keys()):
                    continue
                if notice.visual_range == 0:
                    result_data = {
                        "id": notice.id,
                        "title": notice.title,
                        "content": notice.content,
                        "level": notice.level,
                        "create_time": str(notice.create_time)
                    }
                    result_data_list.append(result_data)
                elif str(user_id) in list(json.loads(notice.notice_status_info).keys()):
                    result_data = {
                        "id": notice.id,
                        "title": notice.title,
                        "content": notice.content,
                        "level": notice.level,
                        "create_time": str(notice.create_time)
                    }
                    result_data_list.append(result_data)
        else:
            for notice in notice_rows:
                if str(user_id) not in list(json.loads(notice.notice_status_info).keys()):
                    continue
                if notice.visual_range == 0 and json.loads(notice.notice_status_info)['id_{}'.format(user_id)] ==0:
                    result_data = {
                        "id": notice.id,
                        "title": notice.title,
                        "content": notice.content,
                        "level": notice.level,
                        "create_time": str(notice.create_time)
                    }
                    result_data_list.append(result_data)
                elif str(user_id) in list(json.loads(notice.notice_status_info).keys()) and json.loads(notice.notice_status_info)['id_{}'.format(user_id)] ==0:
                    result_data = {
                        "id": notice.id,
                        "title": notice.title,
                        "content": notice.content,
                        "level": notice.level,
                        "create_time": str(notice.create_time)
                    }
                    result_data_list.append(result_data)

        return {"code": 200, "data": result_data_list[(page-1)*page_size:page_size*page], "total": len(result_data_list)}


class MyCTFRedBlueList(MyMath):
    """我的ctf红蓝比赛列表"""

    def get_my_score(self, group: CtfRedBlueGroup):
        """获取我的成绩"""

        def conv(score_info):
            if score_info:
                if score_info.change_type == CtfRedBlueScore.ChangeTypeEnum.plus.name:
                    return score_info.score
                return -score_info.score
            else:
                return 0

        user_score = [
            conv(item.score_info)
            for item in group.link_score
            if item and item.user_id == self.user.id
        ]

        return sum(user_score)

    def get_one_group_score(self, **kwargs):
        """获取某个团队各个成员成绩
        最低为0"""
        group_id = kwargs["group_id"]

        res = CtfRedBlueGroup.query.filter(CtfRedBlueGroup.id == group_id)
        if res.count() != 1:
            abort(400, message="查询到0条或多条团队数据，请检查!")

        group = res.first()
        user_info = {
            val.id: {
                "username": val.username,
                "account_name": val.account_name,
                "email": val.email,
            }
            for val in group.relate_user
        }

        score_info: List[LinkCtfRedBlueScoreInfo] = group.link_score

        user_score = defaultdict(list)
        _conver = (
            lambda x, y: x if y == CtfRedBlueScore.ChangeTypeEnum.plus.name else -x
        )

        for item in score_info:
            user_score[item.user_id].append(
                _conver(item.score_info.score, item.score_info.change_type)
            )

        for item in user_info:
            score_list = user_score.get(item) or [0]
            user_info[item]["score"] = sum(score_list)

        return OrderedDict(
            sorted(user_info.items(), key=lambda x: x[1]["score"], reverse=True)
        )

    def get(self):
        params = request.args
        page = params.get("page", type=int, default=1)
        page_size = params.get("pageSize", type=int, default=10)
        keyword = params.get("keyword", type=str)
        status = params.get("status", type=str, default=None)

        filters = []
        if keyword:
            filters.append(CtfRedBlue.name.contains(keyword))

        if status:
            if status not in [val.value for val in CtfRedBlue.StatusChoices]:
                abort(400, message="状态参数错误")
            filters.append(CtfRedBlue.status == status)

        # sqlalchemy 应该生成多个on条件的join语句，此时生成是join及子查询，join中and被忽略
        paginate = (
            CtfRedBlue.query.join(
                CtfRedBlueGroup, CtfRedBlue.id == CtfRedBlueGroup.ctf_red_blue_id
            )
            .filter(*filters, CtfRedBlueGroup.relate_user.any(User.id == self.user.id))
            .distinct()
            .order_by(CtfRedBlue.id.desc())
            .paginate(page, page_size, error_out=False)
        )

        data = {}
        data["current_page"] = page
        data["data"] = []
        if len(paginate.items) > 0:
            for item in paginate.items:
                info = {
                    "id": item.id,
                    "name": item.name,
                    "start_date": item.start_date,
                    "end_date": item.end_date,
                    "status": item.status,
                    "king": item.king,
                    "range_uuid": item.range_uuid,
                }
                initial_score_info = {
                    GroupType.red.value: item.red_initial_score,
                    GroupType.blue.value: item.blue_initial_score,
                }
                group = self.check_ctf_red_blue_is_me(self.user.id, item.id)
                initial_score = initial_score_info.get(group.group_type, 0)
                info.update(
                    {
                        "group_score": self.group_score(
                            group, initial_score=initial_score
                        ),
                        "user_score": self.get_my_score(group),
                        "current_group": group.group_type,
                    }
                )
                # 仅蓝方可查看比赛描述
                if group.group_type == GroupType.blue.value:
                    info["describe"] = item.describe
                data["data"].append(info)
        data = base_detail(data, paginate)
        return {"code": 200, "data": data}


class MyCTFManual(MyMath):
    """学生端 人工裁决模块"""

    def get(self, ctf_red_blue_id=None):
        """当前用户已申请的人工裁决列表"""

        if not ctf_red_blue_id:
            abort(400, message="缺少比赛ID")

        params = request.args
        page = params.get("page", type=int, default=1)
        page_size = params.get("pageSize", type=int, default=10)
        keyword = params.get("keyword", type=str)

        self.check_ctf_red_blue_exists(ctf_red_blue_id)

        filters = []
        if keyword:
            filters.append(CtfRedBlueManualAudit.title.contains(keyword))

        paginate = (
            CtfRedBlueManualAudit.query.filter(*filters)
            .join(
                LinkCtfRedBlueScoreInfo,
                LinkCtfRedBlueScoreInfo.id == CtfRedBlueManualAudit.link_info_id,
            )
            .filter(
                LinkCtfRedBlueScoreInfo.user_id == self.user.id,
                LinkCtfRedBlueScoreInfo.ctf_id == ctf_red_blue_id,
            )
            .order_by(CtfRedBlueManualAudit.create_time.desc())
            .paginate(page, page_size, error_out=False)
        )

        data = {}
        data["current_page"] = page
        data["data"] = []
        if len(paginate.items) > 0:
            for item in paginate.items:
                data["data"].append(
                    {
                        "id": item.id,
                        "title": item.title,
                        "apply_describe": item.apply_describe,  # 申请描述
                        "is_processed": item.is_processed,  # 是否已处理
                        "is_success": item.is_success,  # 是否成功
                        "audit_describe": item.audit_describe,  # 裁决理由
                        "score": item.score,  # 得分
                        "create_time": datetime.timestamp(item.create_time),  # 创建时间
                    }
                )
        data = base_detail(data, paginate)
        return {"code": 200, "data": data}

    def post(self):
        """申请人工裁决"""
        parser = reqparse.RequestParser()
        parser.add_argument(
            "ctf_red_blue_id", type=int, help="缺少红蓝对抗比赛ID", required=True
        )
        parser.add_argument("audit_title", type=str, help="缺少裁决标题", required=True)
        parser.add_argument("audit_describe", type=str, required=False)
        param = parser.parse_args()

        ctf = self.check_ctf_red_blue_exists(param["ctf_red_blue_id"])
        if ctf.status != ctf.StatusChoices.in_progress.value:
            abort(400, message=self.ctf_red_blue_status_map(ctf, msg="非进行中比赛无法申请人工裁决"))
        elif not ctf.range_uuid:
            abort(400, message="比赛尚未关联红蓝靶场，请联系导调或管理员")

        range_manger = RangeManager(range_uuid=ctf.range_uuid, ignore_private=True)
        if range_manger.range_model.Range.is_draft:
            abort(400, message="当前课程/比赛关联的场景需要创建人确认")

        # 暂时不处理附件
        # filename = ""
        # if "file" in request.files:
        #     file = request.files['file']
        #
        #     if file and '.' in file.filename and file.filename.rsplit('.', 1)[1].lower() in ("doc", "docx", "txt"):  # noqa
        #         filename = create_md5(file.filename)
        #         file_path = Path(".").absolute().parent / "path"
        #         if not file_path.exists():
        #             file_path.mkdir()
        #         try:
        #             file.save(Path(file_path, filename))
        #         except Exception as _:
        #             os.remove(file_path)
        #             abort(400, message='文件保存失败, 请重新上传!')
        #     else:
        #         abort(400, message='文件不符合规范, 请重新上传!')

        group = CtfRedBlueGroup.query.filter(
            CtfRedBlueGroup.relate_user.any(User.id == self.user.id),
            CtfRedBlueGroup.ctf_red_blue_id == param["ctf_red_blue_id"],
        ).first()

        try:
            manual_audit = CtfRedBlueManualAudit(
                title=param["audit_title"],
                apply_describe=param["audit_describe"],
                # file_path=filename,
            )
            db.session.add(manual_audit)
            db.session.flush()

            link_score_info = LinkCtfRedBlueScoreInfo(
                ctf_id=param["ctf_red_blue_id"],
                user_id=self.user.id,
                group_id=group.id,
                manual_audit_info=manual_audit,
            )
            db.session.add(link_score_info)
            db.session.commit()
        except Exception as e:
            current_app.logger.exception(e)
            db.session.rollback()
            abort(400, message="申请失败，请重试")
        else:
            return {"code": 200, "data": "申请成功"}

    def delete(self, ctf_red_blue_id):

        if not ctf_red_blue_id:
            abort(400, message="缺少比赛ID")

        parser = reqparse.RequestParser()
        parser.add_argument("audit_id", help="申请裁决的ID是必须的", required=True)
        param = parser.parse_args()

        link_info = LinkCtfRedBlueScoreInfo.query.filter(
            LinkCtfRedBlueScoreInfo.ctf_id == ctf_red_blue_id,
            LinkCtfRedBlueScoreInfo.user_id == self.user.id,
            LinkCtfRedBlueScoreInfo.manual_audit_info.has(
                CtfRedBlueManualAudit.id == param["audit_id"]
            ),
        )

        if link_info.count() != 1:
            abort(400, message="目标数据不存在！")

        # 以后优化文件
        # file = Path(".").absolute().parent / f"path/{audit.file_name}"
        # if file.exists():
        #     os.remove(file)

        # 人工申请将被级连删除
        db.session.delete(link_info.first())
        db.session.commit()

        return {"code": 200, "data": "删除成功"}


class MyCtfRedBlueScore(MyMath):
    """学生端 查看所在团队及成员成绩"""

    def get_one_group_score(self, **kwargs):
        """获取某个团队各个成员成绩
        最低为0"""
        group_id = kwargs["group_id"]

        res = CtfRedBlueGroup.query.filter(CtfRedBlueGroup.id == group_id)
        if res.count() != 1:
            abort(400, message="查询到0条或多条团队数据，请检查!")

        group = res.first()

        score_info: List[LinkCtfRedBlueScoreInfo] = group.link_score
        _conver = (
            lambda x, y: x if y == CtfRedBlueScore.ChangeTypeEnum.plus.name else -x
        )

        user_score = []
        for item in score_info:
            if item.user_id == self.user.id:
                user_score.append(
                    _conver(item.score_info.score, item.score_info.change_type)
                )

        return sum(user_score)

    def get(self, ctf_red_blue_id):

        if not ctf_red_blue_id:
            abort(400, message="缺少比赛ID")

        ctf: CtfRedBlue = self.check_ctf_red_blue_exists(ctf_red_blue_id)

        group = CtfRedBlueGroup.query.filter(
            CtfRedBlueGroup.relate_user.any(User.id == self.user.id),
            CtfRedBlueGroup.ctf_red_blue_id == ctf.id,
        )

        if not group.count():
            abort(400, message="无法找到对应团队！")

        group: CtfRedBlueGroup = group.first()
        initial_score = getattr(ctf, f"{group.group_type}_initial_score", 0)

        group_score = self.group_score(group, initial_score=initial_score)
        users_score = self.get_one_group_score(group_id=group.id)

        return {
            "code": 200,
            "data": {"group_score": group_score, "user_score": users_score},
        }


class MyCTFRedBlueFlag(MyMath):
    """学生端 红蓝对抗flag提交接口"""

    def update_flag_info(
        self, flag: CtfRedBlueFlag, ctf: CtfRedBlue, group: CtfRedBlueGroup, param: dict
    ):
        try:

            if not flag.is_master:
                score = flag.score
                flag.is_used = 1  # 单个flag已使用，防止影响master查询
            else:
                # 核心flag被发现获取所有受影响的模块得分并标记
                # FIXME: 注意 configs.get_config_info.load_image_preset_config 方法
                _filter = [
                    # 注意类型自动转换
                    CtfRedBlueFlag.mark.op("&")(flag.mark) != 0,
                    CtfRedBlueFlag.mark <= flag.mark,
                    CtfRedBlueFlag.ctf_red_blue_id == param["ctf_red_blue_id"],
                    CtfRedBlueFlag.is_used == 0,
                ]
                # 获取受影响的所有flag总分
                score = (
                    db.session.query(func.sum(CtfRedBlueFlag.score))
                    .filter(*_filter)
                    .scalar()
                )
                # 更新flag状态
                model_all_flag = CtfRedBlueFlag.query.filter(*_filter).all()
                for item in model_all_flag:
                    item.is_used = True
                db.session.bulk_save_objects(model_all_flag)

            score_record = CtfRedBlueScore(
                score=score,
                change_type=CtfRedBlueScore.ChangeTypeEnum.plus.name,
                record_type=CtfRedBlueScore.RecordTypeEnum.auto.name,
            )

            db.session.add(score_record)

            link_record = LinkCtfRedBlueScoreInfo(
                ctf_id=ctf.id,
                user_id=self.user.id,
                group_id=group.id,
                score_info=score_record,
                flag_info=flag,
            )
            db.session.add(link_record)
            ctf.link_score.append(link_record)

            if group.group_type == GroupType.red.name:
                blue_group = CtfRedBlueGroup.query.filter(
                    CtfRedBlueGroup.ctf_red_blue_id == param["ctf_red_blue_id"],
                    CtfRedBlueGroup.group_type == GroupType.blue.name,
                ).first()
                blue_record = CtfRedBlueScore(
                    score=score,
                    change_type=CtfRedBlueScore.ChangeTypeEnum.less.name,
                    record_type=CtfRedBlueScore.RecordTypeEnum.link.name,
                )
                db.session.add(blue_record)

                _link_record = LinkCtfRedBlueScoreInfo(
                    ctf_id=ctf.id,
                    group_id=blue_group.id,
                    score_info=blue_record,
                )

                db.session.add(_link_record)
                ctf.link_score.append(_link_record)
        except:  # noqa
            abort(400, message="保存成绩失败，请重试，或提交人工裁决")
        else:
            return {"code": 200, "data": "恭喜flag提交正确"}

    def post(self):
        parser = reqparse.RequestParser()
        parser.add_argument(
            "ctf_red_blue_id", type=int, help="缺少红蓝对抗比赛ID", required=True
        )
        parser.add_argument("flag", type=str, help="缺少flag", required=True)
        param = parser.parse_args()

        ctf: CtfRedBlue = self.check_ctf_red_blue_exists(param["ctf_red_blue_id"])
        if ctf.status != ctf.StatusChoices.in_progress.value:
            abort(400, message=self.ctf_red_blue_status_map(ctf, msg="无法提交flag"))
        elif not ctf.range_uuid:
            abort(400, message="比赛尚未关联红蓝靶场，请联系导调或管理员")

        range_manger = RangeManager(range_uuid=ctf.range_uuid, ignore_private=True)
        if range_manger.range_model.Range.is_draft:
            abort(400, message="当前课程/比赛关联的场景需要创建人确认")

        # 获取用户归属组
        group: CtfRedBlueGroup = CtfRedBlueGroup.query.filter(
            CtfRedBlueGroup.relate_user.any(User.id == self.user.id),
            CtfRedBlueGroup.ctf_red_blue_id == ctf.id,
        ).first()

        # 暂定不会重复
        target_flag_query = CtfRedBlueFlag.query.filter_by(
            ctf_red_blue_id=param["ctf_red_blue_id"],
            flag=param["flag"],
            type=group.group_type,
        )

        if not db.session.query(target_flag_query.exists()).scalar():
            abort(400, message="无法自动判定，答案可能错误或请提交人工裁决")

        flag: CtfRedBlueFlag = target_flag_query.first()

        if flag.is_used:
            abort(400, message="flag分数已获取")

        with db.auto_commit():
            with db.session.begin(subtransactions=True):
                response = self.update_flag_info(flag, ctf, group, param)

        return response
